package main

import (
	"01/redigo/conf"
	"fmt"
	"github.com/gomodule/redigo/redis"
	"log"
	"sync"
	"time"
)

// 创建redis 互斥锁
var rdsLock sync.Mutex

// 定义包级别的全局变量接收实例
var cacheInstance *RedisConn

// 连接对象
type RedisConn struct {
	pool      *redis.Pool
	showDebug bool // 是否需要debug
}

// debug： 自己实现debug 函数
func (rds *RedisConn) Do(commandName string,
	args ...interface{}) (reply interface{}, err error) {
	conn := rds.pool.Get() // 从池中获取链接
	defer conn.Close()     // 将链接的资源 返回到池中

	t1 := time.Now().UnixNano()
	reply, err = conn.Do(commandName, args...) // 执行指令
	if err != nil {
		e := conn.Err() // 当连接不可用时，返回非空值，必须关闭连接。
		log.Println("rdshelper.Do", err, e)
	}
	t2 := time.Now().UnixNano()
	if rds.showDebug {
		fmt.Printf("[redis] [info] [%dus]cmd=%s, err = %s, args=%v, reply=%s\n",
			(t2-t1)/1000, commandName, err, args, reply)
	}
	return reply, err
}

// 设置debug： 函数式编程
func (rds *RedisConn) ShowDebug(b bool) {
	rds.showDebug = b
}

// 得到唯一的redis缓存（池）实例
func InstanceCache() *RedisConn {
	if cacheInstance != nil {
		return cacheInstance
	}
	rdsLock.Lock()
	defer rdsLock.Unlock()

	if cacheInstance != nil {
		return cacheInstance
	}
	return NewCache()
}

// 重新实例化: 创建redis 连接池
func NewCache() *RedisConn {
	pool := redis.Pool{
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", conf.RdsCache.Host, conf.RdsCache.Port))
			if err != nil {
				log.Fatal("rdshelper.NewCache Dial error ", err)
				return nil, err
			}
			return c, nil
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			if time.Since(t) < time.Minute {
				return nil
			}
			_, err := c.Do("PING")
			return err
		},
		MaxIdle:         10000,
		MaxActive:       10000,
		IdleTimeout:     0,
		Wait:            false,
		MaxConnLifetime: 0, // 关闭比这个 持续时长更长的链接。为 0 默认不关闭。
	}
	instance := &RedisConn{
		pool: &pool,
	}
	cacheInstance = instance
	cacheInstance.ShowDebug(true)
	//cacheInstance.ShowDebug(false)
	return cacheInstance
}

type LtUser struct {
	Id         int    `gorm:"primaryKey"`
	Username   string `gorm:"not null; default:''; comment:用户名; type:VARCHAR(50)"`
	Blacktime  int    `gorm:"not null; default:0; comment:黑名单限制到期时间; type:INT(10)"`
	Realname   string `gorm:"not null; default:''; comment:联系人; type:VARCHAR(50)"`
	Mobile     string `gorm:"not null; default:''; comment:手机号; type:VARCHAR(50)"`
	Address    string `gorm:"not null; default:''; comment:联系地址'; type:VARCHAR(255)"`
	SysCreated int    `gorm:"not null; default:0; comment:创建时间; type:int(10)"`
	SysUpdated int    `gorm:"not null; default:0; comment:修改时间; type:int(10)"`
	SysIp      string `gorm:"not null; default:''; comment:IP地址; type:VARCHAR(50)"`
}

func main() {
	// 获得key
	key := "user_123456"
	// 需要导入 reids 实例，
	rds := InstanceCache()
	// 设置锁： 参考文章 https://zhuanlan.zhihu.com/p/62957534
	// 参考set 命令参数设置： http://redisdoc.com/string/set.html
	// EX 设置三秒钟过期时间， NX：只在键不存在时， 才对键进行设置操作
	// 实现的就是： 带有超时限制的细粒度锁《redis 实战》一书中介绍原理。
	// 这里并没有使用 value 值进行处理。key 值就是 uid代表一个用户。细粒度锁。
	// 注意接口类型转换
	//rst, _ := rds.Do("SET", key, 1, "EX", 20, "NX")
	//if rst == "OK" {
	//	fmt.Println("ok")
	//} else {
	//	fmt.Println("no")
	//}
	var user = LtUser{
		Id:         10,
		Username:   "haung",
		Blacktime:  0,
		Realname:   "huang123",
		Mobile:     "15225339797",
		Address:    "西亚斯",
		SysCreated: 0,
		SysUpdated: 0,
		SysIp:      "127.0.0.1",
	}
	// 封装的方便构造参数的
	key = fmt.Sprintf("user_info_%d", user.Id)
	params := redis.Args{}
	params = params.Add(key)
	if user.Username != "" {
		params = params.AddFlat(&user)
	}
	fmt.Println(user)
	fmt.Println(params)
	_, err := rds.Do("HMSET", params...)
	if err != nil {
		fmt.Println("失败 error = ", err)
	} else {
		fmt.Println("成功")
	}

	fmt.Println("获取结果： ")
	rst, _ := redis.StringMap(rds.Do("HGETALL", key))
	fmt.Println(rst)
}
